package mmc.physics;

import mmc.*;
import mmc.cell.Block;
import mmc.server.Scene;
import mmc.server.Server;

public class PhysicsEngine {
	
	private Scene scene; //null on client
	private Position tmp = new Position();
	
	/**
	 * @param scene on client this is null
	 */
	public PhysicsEngine(Scene scene){
		this.scene = scene;
	}
	
	public void update(MovableEngineObject o, float dt){
		if(o.physicsType==PhysicsType.MANAGED){
			if(o.vx==0&&o.vy==0&&o.vz==0)
				return;
			
			tmp.x = o.x;
			tmp.y = o.y;
			tmp.z = o.z;
			Position hit = new Position();
			
			o.x += o.vx*dt;
			if(checkCollission(o, hit)!=null){
				if(o.vx*o.vx < Settings.PHYSICS_ABSORB_SPEED || o.reactionType==PhysicsReactionType.ABSORB){
					o.vx = 0;
					o.x = tmp.x;
				}else if(o.reactionType==PhysicsReactionType.BOUNCE){
					o.vx *= Settings.PHYSICS_BOUNCYNESS;
					o.x = tmp.x;
				}
				o.vy *= Settings.PHYSICS_FRICTION;
				o.vz *= Settings.PHYSICS_FRICTION;
			}
			
			o.vy += Settings.PHYSICS_GRAVITY*dt;
			o.y += o.vy*dt;
			if(checkCollission(o, hit)!=null){
				if(o.vy*o.vy < Settings.PHYSICS_ABSORB_SPEED || o.reactionType==PhysicsReactionType.ABSORB){
					o.vy = 0;
					o.y = tmp.y;
				}else if(o.reactionType==PhysicsReactionType.BOUNCE){
					o.vy *= Settings.PHYSICS_BOUNCYNESS;
					o.y = tmp.y;
				}
				o.vx *= Settings.PHYSICS_FRICTION;
				o.vz *= Settings.PHYSICS_FRICTION;
			}
			
			o.z += o.vz*dt;
			if(checkCollission(o, hit)!=null){
				if(o.vz*o.vz < Settings.PHYSICS_ABSORB_SPEED || o.reactionType==PhysicsReactionType.ABSORB){
					o.vz = 0;
					o.z = tmp.z;
				}else if(o.reactionType==PhysicsReactionType.BOUNCE){
					o.vz *= Settings.PHYSICS_BOUNCYNESS;
					o.z = tmp.z;
				}
				o.vx *= Settings.PHYSICS_FRICTION;
				o.vy *= Settings.PHYSICS_FRICTION;
			}
			
			o.dirty = true;
		}
		
		if(o.vy < Settings.PHYSICS_MAX_FALL_SPEED){
			o.kill();
			Server.instance.broadcast("Killing "+o+" because it's falling too fast (PHYSICS_MAX_FALL_SPEED)");
		}
	}
	
	public void updateClient(MovableEngineObject o, float dt){
		if(o.physicsType==PhysicsType.MANAGED){
			o.x += o.vx*dt;
			o.vy += Settings.PHYSICS_GRAVITY*dt;
			o.y += o.vy*dt;
			o.z += o.vz*dt;
		}
	}
	
	
	public Object checkCollission(EngineObject o, Position hit){
		if(Constants.IS_SERVER)
			for(EngineObject o2 : scene.getObjects()){
				if(o!=o2){
					Position hit2 = getSphereSphereIntersection(o2.boundingSphere,o.boundingSphere);
					if(hit2!=null){
						hit.x=hit2.x;
						hit.y=hit2.y;
						hit.z=hit2.z;
						o.notifyCollission(o2, hit);
						return o2;
					}
				}
			}
		
		byte blockType = scene.getCellManager().getEnclosingBlock(o.x,o.y,o.z);
		if(Block.isSolid(blockType)){
			hit.x = o.x;
			hit.y = o.y;
			hit.z = o.z;
			o.notifyCellCollission(hit);
			return blockType;
		}
		
		return null;
	}
	
	public Position getSphereSphereIntersection(CenteredSphere s1, CenteredSphere s2){
		float dx = s1.center.x - s2.center.x,
				dy = s1.center.y - s2.center.y,
				dz = s1.center.z - s2.center.z;
		
		float d = dx * dx + dy*dy + dz*dz;
		float r = s1.radius + s2.radius;
		d -= r*r;
		
		if(d>0)
			return null;
		else
			return new Position(s2.center.x + dx/2,s2.center.y + dy/2,s2.center.z + dz/2);
	}
}
